package main

import "fmt"

/*
Go 语言中的指针是很容易学习的，Go语言中使用指针可以更加简单的执行一些任务。
Go中的指针不能进行偏移和运算，是安全指针
Go语言中的指针操作非常简单，只需要记住两个符号：&(取地址) 和 *(根据地址取值)。
要搞明白Go语言中的指针需要知道三个概念: 指针地址，指针类型，和指针取值。
*/

func main() {
	/*
		变量的地址：
		每个变量在运行时都拥有一个地址，这个地址代表变量在内存中的位置。
		Go 语言的 取地址符是 & ，放到一个变量前使用就会返回相应变量的内存地址。
	*/
	//获取变量中的内存地址
	var a int = 10
	fmt.Printf("变量的地址：%x\n", &a) //结果就是变量在内存中的地址。

	//什么是指针？
	//一个指针变量就是一个值的内存地址，那么就可以通过这个变量的地址（8_指针）去访问它。
	//取地址的语法   ： prt:=&v      ptr 就是指向变量v的指针
	/*
		其中：
		v: 代表被取地址的变量，类型为T
		ptr：用于接收变量地址的变量，prt 的类型就为 *T ,称做T的指针类型（指向类型T的指针）。* 代表指针。
	*/
	b := 10
	c := &b
	fmt.Printf("a:%d ptr:%d\n", b, &b)
	fmt.Printf("c:%p type:%T value:%d\n", c, c, *c)
	fmt.Println(&c)

	// 指针的声明
	// 在使用指针前，你需要声明指针。
	/*
		指针的声明： var name *var-type
		var-type 为指针类型（指针类型就是指针所指向了一个什么类型的变量），name 为指针名， * 号 用于指定变量是一个指针。
		Go 语言中，的值类型（int 、float、bool、string 、array、struct）都有对应的指针类型，如 *int、*int64、*string 等
	*/
	var ip *int                 //指针ip指向整型变量
	var fp *float32             //指针 fp 执行浮点型变量
	fmt.Println(ip, "----", fp) //<nil> ---- <nil>

	fmt.Println("----------------------指针的使用--------------------------")
	/*
		指针的使用流程：
			1.定义指针变量
			2.为指针变量赋值
			3.访问指针变量中地址指向的值：在指针前面加上 * 号 来获取指针执向的地址块的内容。
	*/
	var d = 20
	var ipd *int
	ipd = &d
	fmt.Printf("d的变量地址是：%x\n", &d)
	fmt.Printf("ipd的变量地址是：%x\n", &ipd)
	//指针变量存储的地址
	fmt.Printf("ipd 变量存储指针地址：%x\n", ipd)
	//使用指针访问值
	fmt.Printf("*ipd 变量的值：%d\n", *ipd)
	/*
		总结：
			1.对变量进行取地址（&）操作，可以获得这个变量的地址（指针变量）。
			2.指针变量的值是原变量的地址。
			3.对指针变量进行取值（*）操作，可以获得指针变量指向的原变量的值
	*/

	//如果只声明指针却不初始化，然后直接使用会报错！
	/*
				错误案例：
					var a *int
					*a=100           //这里的时候就会报错（这里相当与直接使用指针变量进行赋值，实际上它目前还没有分配地址）
					fmt.Println(*a)
				输出结果：
			panic: runtime error: invalid memory address or nil pointer dereference
			[signal 0xc0000005 code=0x1 addr=0x0 pc=0x4bc596]
		原因：
			在Go语言中，对于引用类型的变量，我们在使用的时候不仅要声明它,还要为他分配内存空间（初始化就一种内存分配的方式）
			否则我们的值就没办法存储。对于值类型的声明不需要分配内存空间，是因为他们在声明的时候已经默认分配好了内存空间。
			Go 语言中的new 和 make 是内建的两个函数，主要用来分配内存。
			上面代码中 var a *int 只是声明了一个指针变量a但是没有初始化，指针作为引用数据类型需要初始化后才会拥有内存空间，才可以赋值。

		我们可以使用内置new函数 为指针 a 分配内存（相当于初始化指针，让指针有值，指向一块内存）：
			a := new(int)
			fm.Println(a)
			fmt.Println(*a)

			*a = 100
			fmt.Println(*a)
	*/
	fmt.Println("----------------------空指针--------------------------")
	/*
		当一个指针被定义之后没有分配任何变量时,他的值为nil
		nil 指针也称为空指针
		nil 在概念删和其他语言的null ，None, nil NULL 一样，都指代零值或空值
	*/
	var p *int
	fmt.Println(p)
	fmt.Println("----------------------指针数组--------------------------")

	//定义一个指针数组
	var ptr [3]*int
	fmt.Println(ptr)

	fmt.Println("--------------------指向指针的指针------------------------")
	/*
		Go 支持指向指针的指针。
		如果一个指针变量存放的又是另一个指针变量的地址，则称这个指针变量为指向指针的指针变量。
		当定义一个指向指针的指针变量时，第一个指针存放第二个指针的地址，第二个指针存放变量的地址
	*/
	//例子：
	var e = 100
	var ptre *int
	var pptre **int
	ptre = &e
	pptre = &ptre
	fmt.Println(e)
	fmt.Println(*ptre)
	fmt.Println(**pptre)
	fmt.Println("-------------------向函数传递指针参数----------------------")
	/*
		通过引用或者地址传参，在调用函数时可以改变这块地址中存储的变量的值。
		指针作为函数的参数：
		Go 语言 允许向函数传递指针，只需要在函数定义的参数上设置指针类型即可。
	*/
	//例子
	f := 10
	fmt.Println(f)
	modify1(f) //改变值失败
	fmt.Println(f)
	modify2(&f) //改变值成功
	fmt.Println(f)
}

func modify1(x int) {
	x = 100
}

func modify2(x *int) {
	*x = 100
}
